package io.spring.sample.graphql.jpa;

import com.querydsl.core.group.GroupExpression;
import com.querydsl.core.types.*;
import com.querydsl.core.types.dsl.*;
import com.querydsl.jpa.impl.AbstractJPAQuery;
import io.spring.sample.graphql.util.Tool;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.support.*;
import org.springframework.data.querydsl.EntityPathResolver;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.repository.query.FluentQuery;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import javax.persistence.EntityManager;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;

//import org.springframework.data.jpa.repository.support.FetchableFluentQueryByPredicate;没办法只能复制代码


/**
 * Querydsl specific fragment for extending {@link SimpleJpaRepository} with an implementation for implementation for
 * {@link QuerydslPredicateExecutor}.
 *
 * @author Oliver Gierke
 * @author Thomas Darimont
 * @author Mark Paluch
 * @author Jocelyn Ntakpe
 * @author Christoph Strobl
 * @author Jens Schauder
 * 针对Spring Data JPA入口点QuerydslJpaPredicateExecutor做修改：Page定做为Slice,不需要调用count()做数据库消耗性能的统计。
 * 替换QuerydslJpaPredicateExecutor功能 ； 目的是去掉findAll分页查询的Count(*)功能；同时cache等hints指示必须保留。
 */


//替换QuerydslJpaPredicateExecutor功能 extends QuerydslJpaPredicateExecutor<T>  QuerydslPredicateExecutor<T>
//public class QuerydslNcExecutorImpl<T> extends QuerydslJpaPredicateExecutor<T>  implements QuerydslPredicateExecutor<T>

public class QuerydslNcExecutorImpl<T> extends QuerydslJpaPredicateExecutor<T>  implements QuerydslNcExecutor<T> {
    private final JpaEntityInformation<T, ?> entityInformation;
    private final EntityPath<T> path;
    private final Querydsl querydsl;
    private final EntityManager entityManager;
   // private final CrudMethodMetadata metadata;



    /**
     * Creates a new {@link QuerydslJpaPredicateExecutor} from the given domain class and {@link EntityManager} and uses
     * the given {@link EntityPathResolver} to translate the domain class into an {@link EntityPath}.
     *
     * @param entityInformation must not be {@literal null}.
     * @param entityManager must not be {@literal null}.
     * @param resolver must not be {@literal null}.
     * @param metadata maybe {@literal null}.
     */

    public QuerydslNcExecutorImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager,
                                  EntityPathResolver resolver, @Nullable CrudMethodMetadata metadata) {
        super(entityInformation, entityManager, resolver ,metadata);
        this.path = resolver.createPath(entityInformation.getJavaType());
        this.querydsl = new Querydsl(entityManager, new PathBuilder<T>(path.getType(), path.getMetadata()));
        this.entityInformation = entityInformation;
        this.entityManager = entityManager;
    }


    /* 旧的
    public QuerydslNcExecutorImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager,
                                        EntityPathResolver resolver, @Nullable CrudMethodMetadata metadata) {

        this.entityInformation = entityInformation;
        this.metadata = metadata;
        this.path = resolver.createPath(entityInformation.getJavaType());
        this.querydsl = new Querydsl(entityManager, new PathBuilder<T>(path.getType(), path.getMetadata()));
        this.entityManager = entityManager;
    }

    */


    /*　findAllNc
     * (non-Javadoc)
     * @see org.springframework.data.querydsl.QuerydslPredicateExecutor#findAll(com.querydsl.core.types.Predicate, org.springframework.data.domain.Pageable)
     */
/*    @Override
    public Page<T> findAllNc(Predicate predicate, Pageable pageable) {

        Assert.notNull(predicate, "Predicate must not be null!");
        Assert.notNull(pageable, "Pageable must not be null!");

        //final JPQLQuery<?> countQuery = createCountQuery(predicate); ( JPQLQuery<?> )
        ////JPQLQuery<?>  jpqlQuery= createQuery(predicate);

        JPQLQuery<T> jpqlQuery=createQuery(predicate).select(path);
        //CustomRepositoryImpl.getMyQueryHints()
        //jpqlQuery1.setHint("","");

        JPQLQuery<T> query = querydsl.applyPagination(pageable, jpqlQuery);

        //return PageableExecutionUtils.getPage(query.fetch(), pageable, null);
        return CustomRepositoryImpl.PageableExecutionUtils.getPage(query.fetch(), pageable, null);
    }*/

    /* 父类的findBy()生成sql是全部字段都要读取的。
     * (non-Javadoc)
     * @see org.springframework.data.querydsl.QuerydslPredicateExecutor#findBy(com.querydsl.core.types.Predicate, java.util.function.Function)
     * 这下面的 R 泛型实际类型是箭头函数queryFunction的真实返回类型。T是实体类型；S是实体的某个投影型。
     * 旧的是 <S extends T, R> R findBy（）;
     * findByPi函数里面的createQuery(predicate).select(piExp)才是没执行sql之前的投影概念。querydsl.jpa.impl.AbstractJPAQuery里面FactoryExpression<?> projection实际上是sql查询之后拿到结果再做投影。
     * S extends T; S是子类，T是实体类。 R返回类型，  I是投影类型。
     * fileds里面支持最高两层的嵌套关联字段。
     * 对于有2层嵌套字段的，默认将是cross join的，需改成left join !
     */
    @SuppressWarnings("unchecked")
    @Override
    public <S extends T, R> R findBy(List<String> fileds,Predicate predicate, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction) {

        Assert.notNull(predicate, "Predicate must not be null!");
        Assert.notNull(queryFunction, "Query function must not be null!");
        //下面fluentQuery实例每走1步都会变动的，finder函数里面无法注入queryFunction.apply(fluentQuery):queryFunction{.as()}所隐含的FluentQuery本来应该有的成员变量。
        Function<Sort, AbstractJPAQuery<?, ?>> finder = sort -> {
            //AbstractJPAQuery<?, ?> select = (AbstractJPAQuery<?, ?>) createQuery(predicate).select(path);
            //QUnit qUnit=QUnit.unit;     //FluentQuery:上all或page()都能执行这里！
            List<EntityPath<?>>  joinPaths=new ArrayList<>();     //from Join的关联嵌套对象。
            String basePath="";
            EntityPathBase<?> parentBase=null;
            List<Expression<?>> beanArgs=new ArrayList<>();     //select的字段
            for (String filed : fileds) {
                String[]  basename=filed.split("\\.");
                Expression<?> beanExp=null;
                if(basename.length>1){      //最多支持2层的： fileds必须按顺序罗列， 同一个父对象的字段放在一起的！
                    if(!basePath.equals(basename[0]) ) {
                        parentBase= (EntityPathBase<?>) Tool.reflectGetValueByKey(path, basename[0]);
                        basePath= basename[0];
                        joinPaths.add(parentBase);
                    }
                    beanExp= (Expression<?>) Tool.reflectGetValueByKey(parentBase,basename[1]);   //第三层的关联?
                }
                else {
                    beanExp= (Expression<?>) Tool.reflectGetValueByKey(path, filed);
                }
                if(beanExp instanceof EntityPath<?>) {
                    joinPaths.add((EntityPath<?>) beanExp);
                }
                beanArgs.add(beanExp);
            }
            Map<String,Expression<?>>  mapFieldBind= createBindings(beanArgs.toArray(new Expression<?>[0]));
            QBean<?>  piExp=new QBeanMy<T>(path.getType(), false, mapFieldBind);
           // Expression piExp=Projections.bean(path, beanArgs.toArray(new Expression<?>[0]));
            AbstractJPAQuery<?, ?> select = (AbstractJPAQuery<?, ?>) createQuery(predicate).select(piExp);    //@只能这里筛选select SQL字段。

            for (EntityPath<?> ljPath : joinPaths) {
                select= select.leftJoin(ljPath);
            }

            if (sort != null) {
                select = (AbstractJPAQuery<?, ?>) querydsl.applySorting(sort, select);
            }


            return select;
        };

        BiFunction<Sort, Pageable, AbstractJPAQuery<?, ?>> pagedFinder = (sort, pageable) -> {

            AbstractJPAQuery<?, ?> select = finder.apply(sort);

            if (pageable.isPaged()) {
                select = (AbstractJPAQuery<?, ?>) querydsl.applyPagination(pageable, select);
            }

            return select;
        };

        //要确保这里 能够进入我修改的代码，而不是原始包FetchableFluentQueryByPredicate的代码！
        //旧代码泛型 FetchableFluentQueryByPredicate<T, T> 可能有问题？ S是结果实体投影类, T似原型实体;
        FetchableFluentQueryByPredicate<T, T> fluentQuery = new FetchableFluentQueryByPredicate<>( //
                predicate, //
                this.entityInformation.getJavaType(), //
                finder, //
                pagedFinder, //
                this::count, //
                this::exists, //
                entityManager //
        );
        //QueryDSL调用JPA生成Hql; Querydsl内部也有投影的概念。(R) rst; Object rst=
        return  queryFunction.apply((FluentQuery.FetchableFluentQuery<S>) fluentQuery);
    }
    /**利用interface投影模式， Class<I>是实体类型T的接口类;
     * 对于关联对象的，默认采用 inner join；需改成left join!
     * 这个模式下：不能支持2层关联嵌套字段查询的投影筛选的，第二层对象只能自动join全部都select出来的。
    * */
    public <S extends T, R, I> R findBy(Class<I> pifType,Predicate predicate, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction) {
        Assert.notNull(pifType, "Projection target type must not be null!");
        if (!pifType.isInterface()) {
            throw new UnsupportedOperationException("Class-based DTOs are not yet supported.");
        }
        Class<?>  clazz =pifType;
        List<Method> getFields = new ArrayList<>();
        while (clazz!=null){
            getFields.addAll(Arrays.asList(clazz.getDeclaredMethods()));
            clazz = clazz.getSuperclass();
        }
        List<String> properties=new ArrayList<>();      //Interface-based Projections方案 特殊interface{}
        getFields.forEach(method -> {
            String name=method.getName();
            if(name.startsWith("get")){
                String sBuilder = Character.toLowerCase(name.charAt(3)) +    //接口字段Getter都是这样的： getBxxxyy(), 第一个字符改成小写
                        name.substring(4);
                properties.add(sBuilder);
            }
        });
        return  findBy(properties, predicate, queryFunction);
    }

    /*来自修正：
    protected QBean(Class<? extends T> type, boolean fieldAccess, Expression<?>... args) {
    使用的 private static Map<String,Expression<?>> createBindings(Expression<?>... args) {
    针对字符串findBy(List<String> fileds, 和findBy(Class<I> pifType,两种模式才用的！  而通常findBy(QBeanMy<?> pathSel却不会用的；
    * */
    private  Map<String,Expression<?>> createBindings(Expression<?>... args) {
        Map<String, Expression<?>> rv = new LinkedHashMap<>();
        int omitLen= this.path.toString().length()+1;
        for (Expression<?> expr : args) {
            if (expr instanceof Path<?>) {
                Path<?> path = (Path<?>) expr;
                String key=path.toString().substring(omitLen);      //剔除掉前缀 unit. xxx; <T>=Unit;
                //String key0=path.getMetadata().getName();     //原始版本用的：只能是叶子节点的名字
                rv.put(key, expr);
            } else if (expr instanceof Operation<?>) {
                Operation<?> operation = (Operation<?>) expr;
                if (operation.getOperator() == Ops.ALIAS && operation.getArg(1) instanceof Path<?>) {
                    Path<?> path = (Path<?>) operation.getArg(1);
                    if (isCompoundExpression(operation.getArg(0))) {
                        rv.put(path.getMetadata().getName(), operation.getArg(0));
                    } else {
                        rv.put(path.getMetadata().getName(), operation);
                    }
                } else {
                    throw new IllegalArgumentException("Unsupported expression " + expr);
                }

            } else {
                throw new IllegalArgumentException("Unsupported expression " + expr);
            }
        }
        return Collections.unmodifiableMap(rv);
    }
    //上面Map<String,Expression<?>> createBindings(Expression<?>... args)用到：
    private static boolean isCompoundExpression(Expression<?> expr) {
        return expr instanceof FactoryExpression || expr instanceof GroupExpression;
    }

    @Override
    public <S extends T, R> R findBy(QBeanMy<?> pathSel,Predicate predicate, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction) {

        Assert.notNull(predicate, "Predicate must not be null!");
        Assert.notNull(queryFunction, "Query function must not be null!");
        //下面fluentQuery实例每走1步都会变动的，finder函数里面无法注入queryFunction.apply(fluentQuery):queryFunction{.as()}所隐含的FluentQuery本来应该有的成员变量。
        Function<Sort, AbstractJPAQuery<?, ?>> finder = sort -> {
            //FluentQuery:上all或page()都能执行这里！
            AbstractJPAQuery<?, ?> select = (AbstractJPAQuery<?, ?>) createQuery(predicate).select(pathSel);    //@只能这里筛选select SQL字段。
            List<EntityPath<?>>  joinPaths= pathSel.getLeftJoin();
            for (EntityPath<?> ljPath : joinPaths) {
                select= select.leftJoin(ljPath);
            }

            if (sort != null) {
                select = (AbstractJPAQuery<?, ?>) querydsl.applySorting(sort, select);
            }

            return select;
        };

        BiFunction<Sort, Pageable, AbstractJPAQuery<?, ?>> pagedFinder = (sort, pageable) -> {

            AbstractJPAQuery<?, ?> select = finder.apply(sort);

            if (pageable.isPaged()) {
                select = (AbstractJPAQuery<?, ?>) querydsl.applyPagination(pageable, select);
            }

            return select;
        };

        //要确保这里 能够进入我修改的代码，而不是原始包FetchableFluentQueryByPredicate的代码！
        //旧代码泛型 FetchableFluentQueryByPredicate<T, T> 可能有问题？ S是结果实体投影类, T似原型实体;
        FetchableFluentQueryByPredicate<T, T> fluentQuery = new FetchableFluentQueryByPredicate<>( //
                predicate, //
                this.entityInformation.getJavaType(), //
                finder, //
                pagedFinder, //
                this::count, //
                this::exists, //
                entityManager //
        );

        //QueryDSL调用JPA生成Hql; Querydsl内部也有投影的概念。(R) rst; Object rst=
        return  queryFunction.apply((FluentQuery.FetchableFluentQuery<S>) fluentQuery);
    }

}


